﻿using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

namespace Sage.Encryption
{
    /// <summary>
    /// 提供安全的数据存储功能，使用Windows DPAPI加密保护敏感信息，完全支持AOT编译
    /// </summary>
    [SupportedOSPlatform("windows")]
    public class WindowsSecureDataStorage : IWindowsSecureDataStorage
    {
        private readonly string _companyName;
        private readonly string _applicationName;
        private readonly string _storageDirectory;

        /// <summary>
        /// 初始化安全数据存储类的新实例
        /// </summary>
        /// <param name="companyName">公司名称，用于创建存储文件夹</param>
        /// <param name="applicationName">应用程序名称，用于创建存储文件夹</param>
        public WindowsSecureDataStorage(string companyName, string applicationName)
        {
            ArgumentNullException.ThrowIfNull(companyName);
            ArgumentNullException.ThrowIfNull(applicationName);

            _companyName = companyName;
            _applicationName = applicationName;

            // 构建存储路径（在用户的AppData/Roaming文件夹下）
            string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            _storageDirectory = Path.Combine(appDataPath, _companyName, _applicationName);

            // 确保存储目录存在
            Directory.CreateDirectory(_storageDirectory);
        }

        /// <summary>
        /// 安全地存储数据对象
        /// </summary>
        /// <typeparam name="T">要存储的数据类型</typeparam>
        /// <param name="key">用于标识数据的唯一键</param>
        /// <param name="data">要存储的数据对象</param>
        /// <param name="jsonTypeInfo">源生成的JsonTypeInfo，用于序列化</param>
        /// <exception cref="ArgumentNullException">当参数为null时抛出</exception>
        /// <exception cref="ArgumentException">当key为空字符串时抛出</exception>
        public void SaveData<T>(string key, T data, JsonTypeInfo<T> jsonTypeInfo) where T : class
        {
            ArgumentNullException.ThrowIfNull(key);
            ArgumentNullException.ThrowIfNull(data);
            ArgumentNullException.ThrowIfNull(jsonTypeInfo);

            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentException("键不能为空", nameof(key));

            // 使用源生成的序列化器
            string jsonData = JsonSerializer.Serialize(data, jsonTypeInfo);

            // 将JSON字符串转换为字节数组
            byte[] dataBytes = Encoding.UTF8.GetBytes(jsonData);

            // 使用Windows DPAPI加密数据
            byte[] encryptedData = ProtectedData.Protect(
                dataBytes,
                null,
                DataProtectionScope.CurrentUser);

            // 生成文件路径
            string filePath = GetSecureFilePath(key);

            // 写入加密数据到文件
            File.WriteAllBytes(filePath, encryptedData);
        }

        /// <summary>
        /// 检索并解密存储的数据
        /// </summary>
        /// <typeparam name="T">存储的数据类型</typeparam>
        /// <param name="key">数据的唯一标识键</param>
        /// <param name="jsonTypeInfo">源生成的JsonTypeInfo，用于反序列化</param>
        /// <returns>解密并反序列化后的数据对象</returns>
        /// <exception cref="ArgumentNullException">当参数为null时抛出</exception>
        /// <exception cref="FileNotFoundException">当指定的键对应的数据不存在时抛出</exception>
        /// <exception cref="InvalidOperationException">当反序列化失败时抛出</exception>
        public T GetData<T>(string key, JsonTypeInfo<T> jsonTypeInfo) where T : class
        {
            ArgumentNullException.ThrowIfNull(key);
            ArgumentNullException.ThrowIfNull(jsonTypeInfo);

            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentException("键不能为空", nameof(key));

            // 获取文件路径
            string filePath = GetSecureFilePath(key);

            // 检查文件是否存在
            if (!File.Exists(filePath))
                throw new FileNotFoundException($"找不到键'{key}'对应的安全数据");

            // 读取加密数据
            byte[] encryptedData = File.ReadAllBytes(filePath);

            // 使用Windows DPAPI解密数据
            byte[] decryptedData = ProtectedData.Unprotect(
                encryptedData,
                null,
                DataProtectionScope.CurrentUser);

            // 将字节数组转换回JSON字符串
            string jsonData = Encoding.UTF8.GetString(decryptedData);

            // 使用源生成的反序列化器
            T? result = JsonSerializer.Deserialize(jsonData, jsonTypeInfo);

            return result ?? throw new InvalidOperationException($"无法将数据反序列化为类型 {typeof(T).Name}");
        }

        /// <summary>
        /// 尝试获取数据，如果数据不存在则不抛出异常
        /// </summary>
        /// <typeparam name="T">存储的数据类型</typeparam>
        /// <param name="key">数据的唯一标识键</param>
        /// <param name="jsonTypeInfo">源生成的JsonTypeInfo，用于反序列化</param>
        /// <param name="data">输出参数，成功时包含反序列化的数据</param>
        /// <returns>如果成功获取数据返回true，否则返回false</returns>
        public bool TryGetData<T>(string key, JsonTypeInfo<T> jsonTypeInfo, out T? data) where T : class
        {
            try
            {
                data = GetData(key, jsonTypeInfo);
                if (data is null)
                {
                    data = default;
                    return false;
                }
                return true;
            }
            catch
            {
                data = default;
                return false;
            }
        }

        /// <summary>
        /// 检查指定键的数据是否存在
        /// </summary>
        /// <param name="key">要检查的键</param>
        /// <returns>如果数据存在返回true，否则返回false</returns>
        public bool DataExists(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
                return false;

            return File.Exists(GetSecureFilePath(key));
        }

        /// <summary>
        /// 删除指定键的数据
        /// </summary>
        /// <param name="key">要删除的数据键</param>
        /// <returns>如果数据被成功删除返回true，如果数据不存在返回false</returns>
        public bool DeleteData(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
                return false;

            string filePath = GetSecureFilePath(key);

            if (!File.Exists(filePath))
                return false;

            File.Delete(filePath);
            return true;
        }

        /// <summary>
        /// 更新现有数据，如果数据不存在则创建新数据
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="key">数据键</param>
        /// <param name="updateFunc">更新函数，接收现有数据并返回更新后的数据</param>
        /// <param name="defaultValue">如果数据不存在，用于创建的默认值</param>
        /// <param name="jsonTypeInfo">源生成的JsonTypeInfo，用于序列化和反序列化</param>
        public void UpdateData<T>(string key, Func<T, T> updateFunc, T defaultValue, JsonTypeInfo<T> jsonTypeInfo) where T : class
        {
            ArgumentNullException.ThrowIfNull(updateFunc);
            ArgumentNullException.ThrowIfNull(defaultValue);
            ArgumentNullException.ThrowIfNull(jsonTypeInfo);

            T currentData = defaultValue;

            // 尝试获取现有数据
            if (DataExists(key))
            {
                currentData = GetData(key, jsonTypeInfo);
            }

            // 应用更新函数
            T updatedData = updateFunc(currentData);

            // 保存更新后的数据
            SaveData(key, updatedData, jsonTypeInfo);
        }

        /// <summary>
        /// 获取安全文件的完整路径
        /// </summary>
        /// <param name="key">数据键</param>
        /// <returns>文件的完整路径</returns>
        private string GetSecureFilePath(string key)
        {
            // 将键转换为有效的文件名
            string safeFileName = string.Join("_", key.Split(Path.GetInvalidFileNameChars()));
            return Path.Combine(_storageDirectory, $"{safeFileName}.secure");
        }
    }
}